// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;

using Tierless.Framework.EntityTypes;

namespace Tierless.Framework.Entities
{
	/// <summary>
	/// Tracks the set of lookup entities for a given entity.
	/// </summary>
	[DataContract]
	public class EntityLookups : ICollection<Entity>
	{
		/// <summary>
		/// Initializes a new instance of the EntityLookups class.
		/// </summary>
		/// <param name="entity">The entity for which lookup entities will be tracked.</param>
		public EntityLookups(Entity entity)
		{
			if (entity == null) throw new ArgumentNullException("AEntity");

			_entity = entity;
		}

		[DataMember]
		private Entity _entity;

		/// <summary>
		/// Gets the entity for which the lookup entities are tracked.
		/// </summary>
		public Entity Entity
		{
			get { return _entity; }
		}

		[DataMember]
		private Dictionary<string, Entity> _lookups = new Dictionary<string, Entity>();

		/// <summary>
		/// Gets or sets a lookup entity based on its lookup name.
		/// </summary>
		/// <param name="lookupName">The lookup name derived from the reference describing the relationship between the entities.</param>
		/// <returns>The lookup entity for the given lookup name, if it is set, null otherwise.</returns>
		public Entity this[string lookupName]
		{
			get
			{
				Entity result;
				if (_lookups.TryGetValue(lookupName, out result))
				{
					//Ensure that the lookup reference exists, will throw an exception if it doesn't.
					var reference = _entity.EntityType.References.GetLookupReference(lookupName);
					if (result != null)
					{
						var equal = !reference.SourceColumns.Where((AItem, AIndex) => result[reference.TargetColumns[AIndex].Name] != _entity.Columns[AItem.Name]).Any();

						if (!equal)
						{
							_lookups.Remove(lookupName);
							result = null;
							_lookups.Add(lookupName, null);
						}
					}
				}
				else
					_lookups.Add(lookupName, null);
				return result;
			}
			set
			{
				_lookups[lookupName] = value;

				var reference = _entity.EntityType.References.GetLookupReference(lookupName);
				if (!_entity.IsPopulating && (reference != null))
				{
					if (value != null)
					{
						for (var index = 0; index < reference.SourceColumns.Count; index++)
							if (_entity[reference.SourceColumns[index].Name] != value[reference.TargetColumns[index].Name])
								_entity[reference.SourceColumns[index].Name].Value = value[reference.TargetColumns[index].Name].Value;
					}
					else
					{
						for (var index = 0; index < reference.SourceColumns.Count; index++)
							if (!_entity[reference.SourceColumns[index].Name].IsNull)
								_entity[reference.SourceColumns[index].Name].Clear();
					}
					_entity.DoPropertyChanged(reference.PropertyName);
				}
			}
		}

		internal Dictionary<string, Entity> Entries
		{
			get { return _lookups; }
		}

		#region ICollection<Entity> Members

		public void Add(Entity item)
		{
			throw new InvalidOperationException("Cannot add, entity lookups is a read-only collection");
		}

		public bool Remove(Entity item)
		{
			throw new InvalidOperationException("Cannot remove, entity lookups is a read-only collection");
		}

		public void Clear()
		{
			throw new InvalidOperationException("Cannot clear, entity lookups is a read-only collection");
		}

		public bool Contains(Entity item)
		{
			return _lookups.ContainsValue(item);
		}

		public void CopyTo(Entity[] array, int arrayIndex)
		{
			var values = _lookups.Values.ToArray();
			for (var index = arrayIndex; index < Math.Min(array.Length, Count + arrayIndex); index++)
				array[index] = values[index + arrayIndex];
		}

		public int Count
		{
			get { return _lookups.Count; }
		}

		public bool IsReadOnly
		{
			get { return true; }
		}

		#endregion

		#region IEnumerable<Entity> Members

		public IEnumerator<Entity> GetEnumerator()
		{
			foreach (var reference in _entity.EntityType.References)
			{
				Entity lookup;
				if ((reference.Kind == ReferenceKind.Lookup) && _lookups.TryGetValue(reference.IndexName, out lookup))
					yield return lookup;
			}
		}

		#endregion

		#region IEnumerable Members

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion
	}
}